/*--------------------------------------------------------------------------------------------------
Yatak Operation System Project Copyright 2012
Author : Ilya Churaev
Started : 17 Jun 2012		Updated : 22 Sep 2012
File Name : paging.h		Purpose : Defines the interface for and structures relating to paging.
--------------------------------------------------------------------------------------------------*/
#include <yatak/paging.h>
#include <yatak/mutex.h>
#include <yatak/kheap.h>
#include <yatak/yatak.h>
#include <screen.h>

//#define DEBUG_PAGING

#ifndef DEBUG_PAGING
#define dprintf
#define DBGPRINTF(s)
#endif

// The kernel's page directory
page_directory_t *kernel_directory=0;

// The current page directory;
page_directory_t *current_directory=0;

// Набор bitset для фреймов
uint32 *frames;
uint32 nframes;

volatile uint32 memsize = 0;

// Определенно в kheap.c
extern uint32 placement_address;
extern heap_t *kheap;

// Defined in process.asm
extern void copy_page_physical(unsigned int src,unsigned int page);

// В алгоритме для bitset используются макросы
#define INDEX_FROM_BIT(a) (a/(8*4))
#define OFFSET_FROM_BIT(a) (a%(8*4))

// Статическая функция для установки бита в наборе bitset для фреймов
static void set_frame(uint32 frame_addr)
{
	uint32 frame = frame_addr/PAGE_SIZE;
	uint32 idx = INDEX_FROM_BIT(frame);
	uint32 off = OFFSET_FROM_BIT(frame);
	frames[idx] |= (0x1 << off);
}

// Статическая функция для очистки бита в наборе bitset для фреймов
static void clear_frame(uint32 frame_addr)
{
	uint32 frame = frame_addr/PAGE_SIZE;
	uint32 idx = INDEX_FROM_BIT(frame);
	uint32 off = OFFSET_FROM_BIT(frame);
	frames[idx] &= ~(0x1 << off);
}

// Статическая функция для проверки, установлен ли бит
static uint32 test_frame(uint32 frame_addr)
{
	uint32 frame = frame_addr/PAGE_SIZE;
	uint32 idx = INDEX_FROM_BIT(frame);
	uint32 off = OFFSET_FROM_BIT(frame);
	return (frames[idx] & (0x1 << off));
}

// Статичекая функция для поиска первого свободного фрейма
static uint32 first_frame()
{
	uint32 i, j;
	for (i = 0; i < INDEX_FROM_BIT(nframes); i++)
	{
		if (frames[i] != 0xFFFFFFFF)	// Ничего не освобождаю, сразу выхожу
		{
			// По меньшей мере, здесь 1 свободный бит
			for (j = 0; j < 32; j++)
			{
				uint32 toTest = 0x1 << j;
				if (!(frames[i]&toTest))
					return i*4*8+j;
			}
		}
	}
}

// Функция выделения фрейма
void alloc_frame(page_t *page, int is_kernel, int is_writeable)
{
	if (page->frame != 0)
	{
		return;								// Фрейм уже выделен, сразу возвращаюсь
	}
	else
	{
		uint32 idx = first_frame();			// idx теперь является индексом первого свободного фрейма
		if (idx == (uint32)-1)
		{
			// PANIC макрос
			PANIC ("No free frames!");
		}
		set_frame(idx*PAGE_SIZE);			// Этот фрейм теперь занят!
		page->present = 1;					// Помечаю его присутствие
		page->rw = (is_writeable==1)?1:0;	// Можно ли для страницы выполнять запись?
		page->user = (is_kernel==1)?0:1;	// Находится ли страница в пользовательском режиме?
		page->frame = idx;
	}
}

// Функция для освобождения фрейма
void free_frame(page_t *page)
{
	uint32 frame;
	if (!(frame=page->frame))
	{
		return;								// Указанной страницы теперь фактически нет в выделенном фрейме!
	}
	else
	{
		clear_frame(frame);					// фрейм снова свободен
		page->frame = 0x0;					// Страницы теперь нет во фрейме
	}
}

void init_paging(unsigned int memorysz)
{
	uint32 mem_end_page = memorysz;
	memsize = memorysz;
	
	nframes = mem_end_page / PAGE_SIZE;
	frames = (uint32*)kmalloc/*_a*/(INDEX_FROM_BIT(nframes));
	memset(frames, 0, INDEX_FROM_BIT(nframes));
	    
	// Создаю директорий страниц
	dprintf ("[init_paging] Making a page directory...\n");
	//uint32 phys;
	kernel_directory = (page_directory_t*)kmalloc_a(sizeof(page_directory_t));
	memset((uint8*)kernel_directory, 0, sizeof(page_directory_t));
	kernel_directory->physicalAddr = (uint32)kernel_directory->tablesPhysical;
	
	//This is for our VESA LFB
	uint32 lfb_address = 0xE0000000;		//replace me with a routine
    
	uint32 j = lfb_address;
	while (j < lfb_address+(1024*768*4))
	{
		//If frame is valid...
		if(j+lfb_address+(1024*768*4) < memsize)
			set_frame(j); // Tell the frame bitmap that this frame is now used!
		//Get the page
		page_t *page = get_page(j, 1, kernel_directory);
		//And fill it
		page->present = 1;
		page->rw = 1;
		page-> user = 1;
		page->frame = j / PAGE_SIZE;
		j += PAGE_SIZE;
	}

	// Отображаем несколько страниц в область кучи ядра. Здесь мы вызываем функцию
	// get_page, но не функцию alloc_frame. В результате можно создавать таблицы page_table_t
	// там, где это необходимо. Мы не можем выделить фреймы еще потому, что для них сначала нужно 
	// выполнить взаимно однозначное отображение вниз и мы все еще не можем определить адрес размещения
	// placement_address, используемый между однозначным отображением и механизмом поддержки кучи!
	int i = 0;
	for (i = KHEAP_START; i < KHEAP_START+KHEAP_INITIAL_SIZE; i += PAGE_SIZE)
		get_page(i, 1, kernel_directory);

	// Нам нужна карта идентичности (физический адрес = виртуальный адрес) с адреса
	// 0x0 до конца используемой памяти с тем, чтобы у нас к ним был прозрачный 
	// доступ как если бы страничная организация памяти не использовалась.
	// ЗАМЕТЬТЕ, что мы преднамеренно используем цикл while.
	// Внутри тела цикла мы фактически изменяем адрес placement_address
	// с помощью вызова функции kmalloc(). Цикл while используется здесь, т.к. выход
	// из цикла динамически, а не один раз после запуска цикла.
	i = 0;
	while (i < placement_address+PAGE_SIZE) //0x400000)
	{
		// Код ядра можно читать из пользовательского режима, но нельзя в него записыать
		alloc_frame( get_page(i, 1, kernel_directory), 0, 0);
		i += PAGE_SIZE;
	}
	
	// Теперь размещаем страницы, для которых ранее было выполнено отображение.
	for (i = KHEAP_START; i < KHEAP_START+KHEAP_INITIAL_SIZE; i += PAGE_SIZE)
		alloc_frame( get_page(i, 1, kernel_directory), 0, 0);
	
	// Прежде, чем включить страничное управление памятью, нужно зарегистрировать
	// обработчик некорректного обращения к памяти - page fault.
	dprintf ("[init_paging] Register our page fault handler...\n");
	register_interrupt_handler(14, &page_fault);

	// Теперь включаю страничную адресацию в памяти
	dprintf ("[init_paging] Switching on the page organization of memory...\n");
	switch_page_directory(kernel_directory);

	// Инициализируем кучу ядра
	dprintf ("[init_paging] Creating kernel heap...\n");
	kheap = create_heap(KHEAP_START, KHEAP_START+KHEAP_INITIAL_SIZE, KHEAP_START+KHEAP_MAX_ADDRESS, 0, 0);
	expand(0x1000000, kheap); // Allocate some more space
	//expand(0x400000, kheap);
	dprintf ("[init_paging] Clone directory (0x%x)...\n", kernel_directory);
	current_directory = clone_directory(kernel_directory);
	dprintf ("[init_paging] Switch page directory to 0x%x...\n", current_directory);
	switch_page_directory(current_directory);
	
	// И, вуаля! Вы все сделали! Было бы хорошо сделать такую штуку (которую я сделал в моем коде с примерами), 
	// чтобы функции kmalloc/kfree могли передавать обращения напрямую к функциям alloc/free в случае, если kheap! = 0.
	MessageFoS ("Initialising paging\t\t\t\t\t\t",true);
}

void switch_page_directory(page_directory_t *dir)
{
	current_directory = dir;
	asm volatile("mov %0, %%cr3":: "r"(dir->physicalAddr));
	uint32 cr0;
	asm volatile("mov %%cr0, %0": "=r"(cr0));
	cr0 |= 0x80000000;		// Enable paging!
	asm volatile("mov %0, %%cr0":: "r"(cr0));
}

page_t *get_page(uint32 address, int make, page_directory_t *dir)
{
	// Помещаем адрес в индекс
	address /= PAGE_SIZE;
	// Находим таблицу страниц, в которой есть этот адрес
	uint32 table_idx = address / 1024;
	
	if (dir->tables[table_idx])	// Если эта таблица уже назначена
	{
		return &dir->tables[table_idx]->pages[address%1024];
	}
	else if(make)
	{
		uint32 tmp;
		dir->tables[table_idx] = (page_table_t*)kmalloc_ap(sizeof(page_table_t), &tmp);
		memset(dir->tables[table_idx], 0, PAGE_SIZE);
		dir->tablesPhysical[table_idx] = tmp | 0x7;	// PRESENT, RW, US.
		return &dir->tables[table_idx]->pages[address%1024];
	}
	else
	{
		return 0;
	}
}


void page_fault(registers_t *regs)
{
	DBGPRINTF('!');
	cli();
	// Возникло прерывание неверного обращения к странице - page fault
	// Фдрес прерывания запоминается в регистре CR2
	uint32 faulting_address;
	asm volatile("mov %%cr2, %0" : "=r" (faulting_address));
    
	// Код ошибки подробно сообщает, что случилось
	int present   = !(regs->err_code & 0x1);		// Страница отсутствует
	int rw = regs->err_code & 0x2;					// Операция записи?
	int us = regs->err_code & 0x4;					// Процессор находится в пользовательском режиме
	int reserved = regs->err_code & 0x8;			// В записи страницы перезаписанны биты для нужд процессора
	int id = regs->err_code & 0x10;				// Причина во время выборки инструкции
	
	uint32 cr2;
	asm volatile ("mov %%cr2, %0": "=r"(cr2));
	
	// Выдача сообщения об ошибке
	kprintf("Page fault! ( ");
	if (present) {kprintf("present ");}
	if (rw) {kprintf("read-only ");}
	if (us) {kprintf("user-mode ");}
	if (reserved) {kprintf("reserved ");}
	kprintf(") at 0x%x - EIP: 0x%x\n",faulting_address, regs->eip);
	//if(!strcmp((char*)regs->eip,(char*)cr2))
	//     kprintf("Page fault caused by executing unpaged memory\n");
	//else
	//     kprintf("Page fault caused by reading unpaged memory\n");
	
	if(current_directory != kernel_directory)
	{
		cli();
		//kprintf("Killing task %d\n",getpid());
		panic(regs);
		PANIC("Page fault");
		//exit();
		cli();
		for(;;);
	}
	else
	{
		cli();
		panic(regs);
		PANIC("Page fault");
		cli();
		for(;;);
	}
	PANIC("Page fault");
}

static page_table_t *clone_table(page_table_t *src, uint32 *physAddr)
{
	static mutex_t mutex;
	mutex_enter(&mutex);
	// Создаем новую таблицу страниц, которая выровнена по границе страниц
	page_table_t *table = (page_table_t*)kmalloc_ap(sizeof(page_table_t), physAddr);
	// Обеспечиваем, что бы эта страница была пустой
	memset((uint8*)table, 0, sizeof(page_table_t));
	
	// Для каждой записи в таблице
	int i;
	for (i = 0; i < 1024; i++)
	{
		// If the source entry has a frame associated with it...
		if (!src->pages[i].frame)
			continue;
		// Берем новый фрейм
		alloc_frame(&table->pages[i], 0, 0);
		// Клонируем флаги из оригинала
		if (src->pages[i].present) table->pages[i].present = 1;
		if (src->pages[i].rw)      table->pages[i].rw = 1;
		if (src->pages[i].user)    table->pages[i].user = 1;
		if (src->pages[i].accessed)table->pages[i].accessed = 1;
		if (src->pages[i].dirty)   table->pages[i].dirty = 1;
		// Физически копирую все данные. Эта функция в файле process.asm
		copy_page_physical(src->pages[i].frame*PAGE_SIZE, table->pages[i].frame*PAGE_SIZE);
	}
	mutex_leave(&mutex);
	return table;
}

page_directory_t *clone_directory(page_directory_t *src)
{
	static mutex_t mutex;
	mutex_enter(&mutex);
	uint32 phys;
	// Делаем новый директорий страниц и получаем физический адрес.
	page_directory_t *dir = (page_directory_t*)kmalloc_ap(sizeof(page_directory_t), &phys);
	// Обеспечиваем, чтобы директорий был пуст
	memset((uint8*)dir, 0, sizeof(page_directory_t));
	
	// Берем смещение tablesPhysical от начала структуры page_directory_t
	uint32 offset = (uint32)dir->tablesPhysical - (uint32)dir;
	// Тогда физический адрес dir->tablesPhysical будет следующим
	dir->physicalAddr = phys + offset;

	// Теперь я готов копировать каждую таблицу страниц, если таблица нулевая, ничего не делаю
	int i;
	for (i = 0; i < 1024; i++)
	{
		if (!src->tables[i])
			continue;
		if (kernel_directory->tables[i] == src->tables[i])
		{
			if (!i)
			{
				dprintf ("[clone_directory] kernel_directory->tables[i] = 0x%x src->tables[i] = 0x%x\n",kernel_directory->tables[i], src->tables[i]);
				dprintf ("[clone_directory] kernel_directory->tablesPhysical[i] = 0x%x src->tablesPhysical[i] = 0x%x\n",kernel_directory->tablesPhysical[i], src->tablesPhysical[i]);
			}
			// Она в ядре, так что использую тот же самый указатель
			dir->tables[i] = src->tables[i];
			dir->tablesPhysical[i] = src->tablesPhysical[i];
			if (!i)
			{
				dprintf ("[clone_directory] dir->tables[i] = 0x%x dir->tablesPhysical[i] = 0x%x\n",dir->tables[i], dir->tablesPhysical[i]);
			}
		}
		else
		{
			// Копируем таблицу
			uint32 phys;
			dir->tables[i] = clone_table(src->tables[i], &phys);
			dir->tablesPhysical[i] = phys | 0x07;
			dir->tables[0] = src->tables[0];
			dir->tablesPhysical[0] = src->tablesPhysical[0];
			if (!i)
			{
				dprintf ("[clone_directory] dir->tables[i] = 0x%x dir->tablesPhysical[i] = 0x%x\n",dir->tables[i], dir->tablesPhysical[i]);
			}
		}
	}
	mutex_leave(&mutex);
	return dir;
}

void destroy_directory (page_directory_t *src)
{
	static mutex_t mutex;
	mutex_enter(&mutex);
	int i;
	for (i = 0; i < 1024; i++)
	{
		if (!src->tables[i])
			continue;
		if (src->tables[i] != kernel_directory->tables[i])
		{
			kfree(src->tables[i]);
			src->tables[i] = NULL;
		}
	}
	kfree (src);
	mutex_leave(&mutex);
}

void map_pages(long addr, long size, int rw, int user)
{
	long i = addr;
	while (i < (addr+size+PAGE_SIZE))
	{
		if(i+size < memsize)
			set_frame(i); // Tell the frame bitmap that this frame is now used!
		page_t *page = get_page(i, 1, current_directory);
		page->present = 1;
		page->rw = rw;
		page->user = user;
		page->frame = i / PAGE_SIZE;
		i += PAGE_SIZE;
	}
	return;
}

void virtual_map_pages(long addr, long size, int rw, int user)
{
	long i = addr;
	while (i < (addr+size+PAGE_SIZE))
	{
		if(i+size < memsize)
		{
			//Find first free frame
			set_frame(first_frame());
			//Then we set the space to taken anyway
			kmalloc(PAGE_SIZE);
		}
		page_t *page = get_page(i, 1, current_directory);
		page->present = 1;
		page->rw = rw;
		page->user = user;
		page->frame = i / PAGE_SIZE;
		i += PAGE_SIZE;
	}
	return;
}